package edu.pitt.cs.cs2410pj2.core;

import java.util.ArrayList;

import edu.pitt.cs.cs2410pj2.datamodel.L1CacheEntry;
import edu.pitt.cs.cs2410pj2.datamodel.L1CacheSet;
import edu.pitt.cs.cs2410pj2.datamodel.L2CacheEntry;
import edu.pitt.cs.cs2410pj2.datamodel.L2CacheSet;
import edu.pitt.cs.cs2410pj2.util.AddrInfo;
import edu.pitt.cs.cs2410pj2.util.AddrProcessor;
import edu.pitt.cs.cs2410pj2.util.ByteStrOperator;
import edu.pitt.cs.cs2410pj2.util.Parameters;

/**
 * L2Cache
 * 
 * @author zhangfan
 * 
 */
public class L2Cache {
	private Parameters para;
	private int tileId;

	private ArrayList<L2CacheSet> sets = new ArrayList<L2CacheSet>();

	public L2CacheSet getCacheSet(int setIndex) {
		return sets.get(setIndex);
	}

	public Parameters getPara() {
		return para;
	}

	public void setPara(Parameters para) {
		this.para = para;
	}

	public int getTileId() {
		return tileId;
	}

	public void setTileId(int tileId) {
		this.tileId = tileId;
	}

	public ArrayList<L2CacheSet> getSets() {
		return sets;
	}

	public void setSets(ArrayList<L2CacheSet> sets) {
		this.sets = sets;
	}

	public L2Cache(Parameters para) {
		this.para = para;
		int m = para.n2 - para.a2 - para.b;
		int setNum = (int) Math.pow(2, m);
		for (int i = 0; i < setNum; i++) {
			L2CacheSet set = new L2CacheSet(para);
			set.setTileId(tileId);
			set.setSetid(i);
			sets.add(set);
		}
	}

	public L2Cache(Parameters para, int tileId) {
		this.para = para;
		int m = para.n2 - para.a2 - para.b;
		int setNum = (int) Math.pow(2, m);
		for (int i = 0; i < setNum; i++) {
			L2CacheSet set = new L2CacheSet(para, tileId, i);
			sets.add(set);
		}
	}

//	public String generateKey(int setIndex, int cacheTag) {
//		return ByteStrOperator.decToBinary(setIndex)
//				+ ByteStrOperator.decToBinary(cacheTag);
//	}

	// add a block, in L2Cache, if the set is full, there is no need to take
	// care of the replaced LRU, but returns the replaced block address
	public String addBlock(int setIndex, int cacheTag, int timeCycle,
			String data) {
		if (findBlock(setIndex, cacheTag) != null) {
			// This only happens when there are multiple loads, the data is then
			// replaced with the new load
			findBlock(setIndex, cacheTag).setData(data);
		} else {
			if (sets.get(setIndex).isFull()) {
				int replacedCacheTag = sets.get(setIndex).replaceLRU(cacheTag,
						timeCycle, data);
				String key = TileDirectory.generateKey(setIndex, replacedCacheTag);
				return key;
			} else {
				sets.get(setIndex).addBlock(cacheTag, timeCycle, data);
			}
		}
		return null;
	}

	// Find a block with specified setIndex and cacheTag
	public L2CacheEntry findBlock(int setIndex, int cacheTag) {
		return sets.get(setIndex).findBlock(cacheTag);
	}

	// Find a block with the block Addr
	public L2CacheEntry findBlock(String blockAddr) {
		AddrInfo info = AddrProcessor.processAddr(blockAddr, para.p, para.n2,
				para.a2, para.b);
		return findBlock(info.getSetIndex(), info.getCacheTag());
	}
}
